/**
 * Copyright (c) 2012 Ibrahim Abd Elkader 
 * Email: <i.abdalkader@gmail.com> 
 * Licensed under the MIT license.
 * See the file COPYING for copying permission.
 */
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include "utask.h"
#include "utask.h"
#include "sched.h"
/* main context */
ucontext_t uctx_main;
/* task scheduler */
static struct sched_t *sched = NULL;

void timer_handler(int sig, siginfo_t *si, void *uc)
{
    sched->tick();
}

void create_timer(int ms)
{
    timer_t timerid;
    struct sigevent sev;
    struct itimerspec value;

    /* Create the timer */
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &timerid;    
    timer_create(CLOCK_REALTIME, &sev, &timerid);

    /* set the timeout */
    value.it_value.tv_sec = 0;
    value.it_value.tv_nsec = ms * 1000000;
    value.it_interval.tv_sec = 0;
    value.it_interval.tv_nsec = ms * 1000000;
    timer_settime(timerid, 0, &value, NULL);
}

int utask_init(struct sched_t *s)
{
    struct sigaction sa;

    /* timer signal handler */
    sa.sa_sigaction = timer_handler;
    sa.sa_flags = SA_SIGINFO|SA_RESTART;
    
    sigemptyset(&sa.sa_mask);
    sigaction(SIGRTMIN, &sa, NULL);

    /* use cooperative scheduling by default */
    if (s != NULL) {
        sched = s;
    } else {
        sched = sched_co_alloc();
    }
    return 0;
}

int utask_create(task_callback cb, void *arg, int ss_size)
{
    static int id=1;
    struct task_t *task;

    /* alloc new task struct */
    task = (struct task_t*) calloc(1, sizeof(ucontext_t));
    task->id = id++;

    /* get the current execution context */
    if (getcontext(&task->uctx) == -1) {
        free(task);
        return -1;
    }
    
    /* link the new context to the main context such that 
       when it returns it falls back to the main context*/
    task->uctx.uc_link = &uctx_main;

    /* allocate a stack for the new task */
    task->uctx.uc_stack.ss_size = ss_size;
    task->uctx.uc_stack.ss_sp = malloc(ss_size);   

    /* create new context */
    makecontext(&task->uctx, (void (*)())cb, 1, arg);

    /* tell sched about the new task */
    sched->create(task);

    printf("task%d created\n", task->id);
    return 0;
}

void utask_destroy()
{    
    /* ask the scheduler to remove the task */
    sched->destroy();
}

void utask_free(struct task_t *task)
{
    printf("task%d destroyed time:%ld\n", task->id, task->ticks);
    /* free task stack */
    free(task->uctx.uc_stack.ss_sp);
    /* free task struct */
    free(task);
}

void utask_run()
{
    /* start timer */
    create_timer(1);
    /* start the scheduler */
    sched->run();
}

void utask_yield()
{
    sched->yield();
}

long utask_time_us()
{
    struct timeval tv;
	gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

